/**
 * @Author: colpu
 * @Date: 2019-03-20 14:28:16
 * @Last Modified by: colpu
 * @Last Modified time: 2021-07-09 15:43:50
 */

/**
 * @class Reflush下拉刷新和上拉加载js组件
 * @version 1.0.0
 * @author colpu <ycg520520@qq.com> 靠谱
 */
class Reflush {
  constructor(options) {
    const _this = this;
    // 版本号
    _this.version = '1.0.0';
    // 基础默认配置
    const DEFAULTOPTIONS = {
      element: document.body
    };
    // 获取os
    _this.getOs();

    // 滑动区域是否为body
    _this.isBodyScroll = !!_this.options.element;
    _this.options.element = _this.getElement(_this.options.element);
    // 合并配置
    _this.options = _this.assign(DEFAULTOPTIONS, options)
    // 滑动元素区域
    _this.element = _this.options.element;

  }

  /**
   * @function scrollTo 滑动列表到指定位置--带缓冲效果
   * @description
   * (y = 0 回到顶部; 如果要滚动到底部可以传一个较大的值, 比如99999);
   t时长, 单位ms, 默认300
   * @param {*} y
   * @param {*} t
   */
  scrollTo(y, t) {
    let _this = this;
    let star = _this.getScrollTop();
    let end = y;
    if (end > 0) {
      let maxY = _this.getScrollHeight() - _this.getClientHeight(); // y的最大值
      if (end > maxY) end = maxY; // 不可超过最大值
    } else {
      end = 0; // 不可小于0
    }
    _this.isScrollTo = true; // 标记在滑动中,阻止列表的触摸事件
    _this.scrollDom.style.webkitOverflowScrolling = 'auto';
    _this.getStep(star, end, function (step) {
      _this.setScrollTop(step);
      if (step === end) {
        _this.scrollDom.style.webkitOverflowScrolling = 'touch';
        _this.isScrollTo = false;
      }
    }, t)
  }

  /**
   * @function assign 深度合并扩展
   * @param {Object} target 要扩展对象
   * @return {Object} 返回合并后的对象
   */
  assign(...args) {
    // 如果目标不存在或目标为undefined,null
    if (!args[0]) {
      throw new TypeError('无法将未定义或空值转换为对象');
    }

    let to = Object(target);
    const typeCall = Object.prototype.toString
    for (let i = 1, len = args.length; i < len; i++) {
      let nextSource = args[i];
      // 如果目标不存在或目标为undefined,null
      if (nextSource) {
        for (let nextKey in nextSource) {
          if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
            const value = nextSource[nextKey];
            const origin = to[nextKey];
            if (typeof origin === 'object' && typeCall.call(origin) === typeCall.call(value)) {
              to[nextKey] = _this.assign(origin, value)
            } else {
              to[nextKey] = value;
            }
          }
        }
      }
    }
    return to;
  }

  /**
   * @function getOS 获得当前系统
   * @return {Object} 返回是否为ios,pc,android
   */
  getOS() {
    const u = navigator.userAgent;
    const isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); // 是否为ios设备
    const isPC = typeof window.orientation === 'undefined'; // 是否为PC端
    const isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; // 是否为android端

    this.os = {
      ios: isIOS,
      pc: isPC,
      android: isAndroid
    }
  }

  /**
   * @function isVisible 元素是否在列表垂直区域可视
   * @description
   * (不考虑scrollLeft的情况,
   * 因为没法监听每个元素的水平滚动事件,
   * 也不考虑translateY的情况, 因为情况比较少, 且会增加计算的复杂度)
   * @param {Element} element 当前元素
   * @param {Number} offset 可视区域上下偏移的距离
   */
  isVisible(element, offset) {
    const _this = this;
    offset = offset || 0;
    // 元素顶部到容器顶部的距离
    let top = _this.getOffsetTop(element);
    // 滚动条的位置(可视范围的顶部)
    let topSee = _this.getScrollTop() - offset;
    // 元素底部到容器顶部的距离
    let bottom = top + element.offsetHeight;
    // 滚动条的位置+容器高度(可视范围的底部)
    let bottomSee = topSee + offset + _this.getClientHeight() + offset;
    // 图片顶部在可视范围内 || 图片底部在可视范围; 不考虑scrollLeft和translateY的情况
    return (top < bottomSee && top >= topSee) || (bottom <= bottomSee && bottom > topSee);
  }

  /**
   * @function getOffsetTop 获取元素到刷新元素滚动列表顶部的距离
   * @param {String|Element} element
   */
  getOffsetTop(element) {
    var top = element.offsetTop;
    var parent = element.offsetParent;
    while (parent != null && parent !== this.element) {
      top += parent.offsetTop + parent.clientTop;
      parent = parent.offsetParent;
    }
    return top;
  }
  /**
   * @function getElement 获得elment
   * @param {String|Element} id
   */
  getElement(id) {
    let dom;
    if (id) {
      // 如果是string，则根据id查找，否则则直接赋值
      if (typeof id === 'string') {
        dom = document.getElementById(id);
      } else if (id.nodeType) {
        dom = id;
      }
    }
    if (!dom) {
      console.error(`id为${id}的DOM元素不能找到!`);
    }
    return dom;
  }

  /**
   * @function getScrollHeight 滚动内容的高度
   * @return {Number} 返回滚动内容的高度
   */
  getScrollHeight() {
    return this.element.scrollHeight;
  }

  /**
   * @function getClientHeight 获得窗口高度
   * @return {Number} 返回获得窗口高度
   */
  getClientHeight() {
    if (this.isBodyScroll && document.compatMode === 'CSS1Compat') {
      return document.documentElement.clientHeight;
    } else {
      return this.element.clientHeight;
    }
  }

  /**
   * @function getBodyHeight 获得文档高度
   * @return {Number} 返回文档高度
   */
  getBodyHeight() {
    return document.body.clientHeight || document.documentElement.clientHeight;
  }

  /**
   * @function getScrollTop 获得滚动条的位置
   * @return {Number} 返回滚动条的位置
   */
  getScrollTop() {
    if (this.isScrollBody) {
      return document.documentElement.scrollTop || document.body.scrollTop;
    } else {
      return this.element.scrollTop;
    }
  }

  /**
   * @function getToBottom 获得距离底部位置
   * @return {Number} 返回距离底部的位置
   */
  getToBottom() {
    return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop();
  }

  /**
   * @function setScrollTop 设置滚动条的位置
   * @param {Number} top 设置滚动条滚动距离
   */
  setScrollTop(top) {
    if (typeof top === 'number') {
      if (this.isBodyScroll) {
        document.documentElement.scrollTop = top;
        document.body.scrollTop = top;
      } else {
        this.element.scrollTop = top;
      }
    }
  }
}
